package simpledb.transaction;

import simpledb.common.Permissions;
import simpledb.storage.Page;
import simpledb.storage.PageId;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class LockManager {
    private Map<PageId, TransactionId> pageWriteHolder;
    private final  Map<PageId, Set<TransactionId>> pageReadHolders;
    private static final int MINTIME = 100, MAXLEN = 900;
    private final Random random = new Random();
    public LockManager() {
        pageReadHolders =new ConcurrentHashMap<>();
        pageWriteHolder=new ConcurrentHashMap<>();
    }
    public void acquire(TransactionId tid, PageId pid, Permissions p) {
        if (p == Permissions.READ_ONLY) {
            acquireReadLock(tid, pid);
        } else {
            acquireWriteLock(tid, pid);
        }
    }
    //获取读锁。
    private void acquireReadLock(TransactionId tid, PageId pid){
        //首先判断其自身是否持有锁，如果自身持有锁的话，那么我选择不执行，等待锁被释放。然后再看其是否被写锁持有。
        if(!hold(tid, pid)){
            synchronized (pid){
                Thread thread = Thread.currentThread();
                Timer timer = new Timer(true);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        thread.interrupt();
                    }
                }, MINTIME + random.nextInt(MAXLEN));
                while(pageWriteHolder.containsKey(pid)){
                    try {
                        pid.wait(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                timer.cancel();
                pageReadHolders.computeIfAbsent(
                        pid, key->new HashSet<>()).add(tid);
            }
        }

    }

    private void acquireWriteLock(TransactionId tid, PageId pid){
        if(!hold(tid,pid)){
            synchronized (pid){
                Thread thread = Thread.currentThread();
                Timer timer = new Timer(true);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        thread.interrupt();
                    }
                }, MINTIME + random.nextInt(MAXLEN));
                while(pageWriteHolder.containsKey(pid)||haveOtherReader(tid,pid)){
                    try {
                       pid.wait(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                timer.cancel();
                pageWriteHolder.put(pid,tid);
            }
        }
    }


    //判断某个页是否被其它读锁拿着。hashset的唯一性应该是比较好的，也许可以直接先去查看是否有自己，再去直接判断其数量即可。
    private boolean haveOtherReader(TransactionId tid, PageId pid) {
        synchronized (pid) {
            if (pageReadHolders.containsKey(pid)) {
                for (TransactionId t : pageReadHolders.get(pid)) {
                    if (!t.equals(tid)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    public void releaseAll(TransactionId tid) {
        for (PageId pid : pageWriteHolder.keySet()) {
            releaseWriteLock(tid, pid);
        }
        for (PageId pid : pageReadHolders.keySet()) {
            releaseReadLock(tid, pid);
        }
    }
    public void release(TransactionId tid, PageId pid) {
        releaseWriteLock(tid, pid);
        releaseReadLock(tid, pid);
    }
    private void releaseReadLock(TransactionId tid, PageId pid) {
        if (holdReadLock(tid, pid)) {
            synchronized (pid) {
                pageReadHolders.get(pid).remove(tid);
                if (pageReadHolders.get(pid).isEmpty()) {
                    pageReadHolders.remove(pid);
                }
            }
        }
    }
    private void releaseWriteLock(TransactionId tid, PageId pid) {
        if (holdWriteLock(tid, pid)) {
            synchronized (pid) {
                pageWriteHolder.remove(pid);
            }
        }
    }


    //判断某个事务是否持有读锁。首先判断该页是否在读锁管理器中，然后判断该事务是否持有该页的set中。
    private boolean holdReadLock(TransactionId tid, PageId pid){
        synchronized (pid){
            return pageReadHolders.containsKey(pid)&&pageReadHolders.get(pid).contains(tid);
        }
    }
    //判断某个事务是否持有写锁，直接去拿对对应的事务id即可。
    private boolean holdWriteLock(TransactionId tid, PageId pid) {
        synchronized (pid) {
            return pageWriteHolder.containsKey(pid) &&
                    pageWriteHolder.get(pid).equals(tid);
        }
    }
    //判断是否有别的事务持有读锁或者写锁。
    public boolean hold(TransactionId tid, PageId pid) {
        return holdReadLock(tid, pid) || holdWriteLock(tid, pid);
    }

}
